﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml;
using Brush = System.Drawing.Brush;
using Color = System.Drawing.Color;
using Pen = System.Drawing.Pen;
using Size = System.Drawing.Size;

namespace Look_Through_Windows.Helper
{
    public static class Extensions
    {
        /// <summary>
        /// Rerenders the specified Bitmap.
        /// </summary>
        /// <param name="lhs">The Bitmap.</param>
        /// <param name="colorTransform">The color transform.</param>
        /// <returns></returns>
        public static Bitmap Rerender( this Bitmap lhs, Func<Color, Color> colorTransform )
        {
            var colors = lhs.ToArray( ( r, g, b, a, x, y ) => Color.FromArgb( a, r, g, b ) );
            return colors.Render( ( c, x, y ) => colorTransform( c ) );
        }

        /// <summary>
        /// Clamp
        /// </summary>
        public static int ClampTo( this int i, int min, int max )
        {
            if ( i < min ) return min;
            if ( i > max ) return max;
            return i;
        }

        /// <summary>
        /// Clamp
        /// </summary>
        public static float ClampTo( this float i, float min, float max )
        {
            if ( i < min ) return min;
            if ( i > max ) return max;
            return i;
        }

        /// <summary>
        /// Does something and returns the object
        /// </summary>
        public static T DoAndReturn<T>( this T obj, Action<T> a )
        {
            a( obj );
            return obj;
        }

        /// <summary>
        /// Gets a minimal bounding box around non-zero pixels
        /// </summary>
        public static Int32Rect GetMinRect( this int[] pixels, int width, int height )
        {
            int minx = width, maxx = 0, miny = height, maxy = 0;
            int i = 0;
            int changedPixels = 0;
            for ( int y = 0 ; y < height ; ++y )
                for ( int x = 0 ; x < width ; ++x )
                    if ( pixels[ i++ ] != 0 )
                    {
                        minx = Math.Min( minx, x );
                        miny = Math.Min( miny, y );
                        maxx = Math.Max( maxx, x );
                        maxy = Math.Max( maxy, y );
                        ++changedPixels;
                    }
            ++maxx;
            ++maxy;

            if ( changedPixels == 0 ) throw new InvalidOperationException( );

            return new Int32Rect(
                minx, miny,
                maxx - minx,
                maxy - miny
                );
        }

        /// <summary>
        /// Gets a subrect
        /// </summary>
        public static int[] GetSubArray( this int[] buffer, Int32Rect subrect, int width, int height )
        {
            int uwidth = subrect.Width;
            int uheight = subrect.Height;
            int[] newbuffer = new int[ uwidth * uheight ];
            for ( int y = 0 ; y < uheight ; ++y )
                for ( int x = 0 ; x < uwidth ; ++x )
                    newbuffer[ y * uwidth + x ] = buffer[ ( y + subrect.Y ) * width + ( x + subrect.X ) ];
            return newbuffer;
        }

        /// <summary>
        /// Converts the content of this image to a PNG and returns the bytes
        /// </summary>
        public static byte[] ToPNGByteArray( this IntPtr buffer, int width, int height )
        {
            /*WriteableBitmap bmp = new WriteableBitmap( width, height, 96, 96, PixelFormats.Bgr32, null );
            bmp.WritePixels( new Int32Rect( 0, 0, width, height ), buffer, width * height * 4, width * 4 );

            PngBitmapEncoder pngEncoder = new PngBitmapEncoder( );
            pngEncoder.Frames.Add( BitmapFrame.Create( bmp ) );
            MemoryStream ms = new MemoryStream( );
            pngEncoder.Save( ms );

            return ms.ToArray( );*/


            Bitmap bmp = new Bitmap( width, height );
            var data = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.ReadWrite, bmp.PixelFormat );
            WindowExtract.memcpy( data.Scan0, buffer, new UIntPtr( ( ulong )( width * height * 4 ) ) );
            bmp.UnlockBits( data );
            MemoryStream ms = new MemoryStream( );
            bmp.Save( ms, ImageFormat.Png );
            return ms.ToArray( );
        }
        /// <summary>
        /// Converts the content of this image to a PNG and returns the bytes
        /// </summary>
        public static byte[] ToPNGByteArray( this int[] buffer, int width, int height )
        {
            //Stopwatch sw = new Stopwatch( );
            byte[] newbuffer;
            {
                Bitmap bmp = new Bitmap( width, height );
                bmp.Fill( buffer );
                MemoryStream ms = new MemoryStream( );
                bmp.Save( ms, ImageFormat.Png );
                newbuffer = ms.ToArray( );

                /*byte[] bytes = new byte[ width * height * 4 ];
                GCHandle handle = GCHandle.Alloc( bytes, GCHandleType.Pinned );
                Marshal.Copy( buffer, 0, handle.AddrOfPinnedObject( ), width * height );
                handle.Free( );
                sw.Start( );
                using ( MemoryStream mms = new MemoryStream( ) )
                {
                    using ( DeflateStream comp = new DeflateStream( mms, CompressionLevel.Fastest, false ) )
                    {
                        comp.Write( bytes, 0, bytes.Length );
                    }
                    var cbytes = mms.ToArray( );
                    sw.Stop( );
                    Console.WriteLine( cbytes.Length + " <-> " + newbuffer.Length );
                }*/
            }
            {
                /*WriteableBitmap bmp = new WriteableBitmap( width, height, 96, 96, PixelFormats.Bgr32, null );
                bmp.WritePixels( new Int32Rect( 0, 0, width, height ), buffer, width * 4, 0 );

                PngBitmapEncoder pngEncoder = new PngBitmapEncoder( );
                pngEncoder.Frames.Add( BitmapFrame.Create( bmp ) );
                MemoryStream ms = new MemoryStream( );
                pngEncoder.Save( ms );

                newbuffer = ms.ToArray( );*/
            }
            //Console.WriteLine( width + " x " + height + " -> " + sw.Elapsed.TotalSeconds.ToDurationString( ) );
            return newbuffer;
        }

        /// <summary>
        /// Converts a byte array back to a png
        /// </summary>
        public static WriteableBitmap ToPNG( this byte[] buffer )
        {
            PngBitmapDecoder pngDecoder = new PngBitmapDecoder( new MemoryStream( buffer ), BitmapCreateOptions.None, BitmapCacheOption.None );
            return new WriteableBitmap( new FormatConvertedBitmap( pngDecoder.Frames[ 0 ], PixelFormats.Bgr32, null, 0 ) );
        }

        /// <summary>
        /// Makes a neat formatted duration
        /// </summary>
        public static string ToDurationString( this float seconds )
        {
            if ( seconds < 1 / 1000f ) return ( seconds * 1000 * 1000 ).ToString( "0.0" ) + " μs";
            if ( seconds < 1 ) return ( seconds * 1000 ).ToString( "0.0" ) + " ms";
            return ( seconds ).ToString( "0.0" ) + " sec";
        }
        /// <summary>
        /// Makes a neat formatted duration
        /// </summary>
        public static string ToDurationString( this double seconds )
        {
            if ( seconds < 1 / 1000f ) return ( seconds * 1000 * 1000 ).ToString( "0.0" ) + " μs";
            if ( seconds < 1 ) return ( seconds * 1000 ).ToString( "0.0" ) + " ms";
            return ( seconds ).ToString( "0.0" ) + " sec";
        }

        /// <summary>
        /// Makes this power of two
        /// </summary>
        public static Size MakePowerOfTwo( this Size s )
        {
            int w = 1, h = 1;
            while ( w < s.Width ) w *= 2;
            while ( h < s.Height ) h *= 2;
            return new Size( w, h );
        }

        /// <summary>
        /// Makes this power of two
        /// </summary>
        public static int MakePowerOfTwo( this double s )
        {
            int x = 1;
            while ( x < s ) x *= 2;
            return x;
        }

        /// <summary>
        /// Makes this power of two
        /// </summary>
        public static int MakePowerOfTwo( this int s )
        {
            int x = 1;
            while ( x < s ) x *= 2;
            return x;
        }

        /// <summary>
        /// Makes this power of two
        /// (Generates always a new bmp!)
        /// </summary>
        public static Bitmap MakePowerOfTwo( this Bitmap bmp )
        {
            int w = bmp.Width.MakePowerOfTwo( );
            int h = bmp.Height.MakePowerOfTwo( );
            Bitmap newBmp = new Bitmap( w, h );
            using ( Graphics g = Graphics.FromImage( newBmp ) )
            {
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.DrawImage( bmp, 0, 0, w, h );
            }
            return newBmp;
        }

        /// <summary>
        /// Renders a 2d array [y,x] !!! to a bitmap
        /// </summary>
        public static Bitmap Render<T>( this T[ , ] map, Func<T, int, int, Color> renderer )
        {
            int width = map.GetLength( 1 );
            int height = map.GetLength( 0 );
            Bitmap bmp = new Bitmap( width, height );
            byte[] colors = new byte[ width * height * 4 ];
            Parallel.For( 0, height, y =>
            {
                int currIndex = y * width * 4;
                for ( int x = 0 ; x < width ; ++x )
                {
                    Color c = renderer( map[ y, x ], x, y );
                    colors[ currIndex + 0 ] = c.B;
                    colors[ currIndex + 1 ] = c.G;
                    colors[ currIndex + 2 ] = c.R;
                    colors[ currIndex + 3 ] = c.A;
                    currIndex += 4;
                }
            } );
            bmp.Fill( colors );
            return bmp;
        }
        /// <summary>
        /// Renders a 2d array [y,x] !!! to a bitmap (y mirrored)
        /// </summary>
        public static Bitmap RenderMirrorY<T>( this T[ , ] map, Func<T, int, int, Color> renderer )
        {
            int width = map.GetLength( 1 );
            int height = map.GetLength( 0 );
            Bitmap bmp = new Bitmap( width, height );
            byte[] colors = new byte[ width * height * 4 ];
            Parallel.For( 0, height, y =>
            {
                int currIndex = y * width * 4;
                for ( int x = 0 ; x < width ; ++x )
                {
                    Color c = renderer( map[ height - y - 1, x ], x, y );
                    colors[ currIndex + 0 ] = c.B;
                    colors[ currIndex + 1 ] = c.G;
                    colors[ currIndex + 2 ] = c.R;
                    colors[ currIndex + 3 ] = c.A;
                    currIndex += 4;
                }
            } );
            bmp.Fill( colors );
            return bmp;
        }
        /// <summary>
        /// Renders a 2d array [x,y] !!! to a bitmap
        /// </summary>
        public static Bitmap RenderXY<T>( this T[ , ] map, Func<T, int, int, Color> renderer )
        {
            int width = map.GetLength( 1 );
            int height = map.GetLength( 0 );
            Bitmap bmp = new Bitmap( width, height );
            byte[] colors = new byte[ width * height * 4 ];
            Parallel.For( 0, height, y =>
            {
                int currIndex = y * width * 4;
                for ( int x = 0 ; x < width ; ++x )
                {
                    Color c = renderer( map[ x, y ], x, y );
                    colors[ currIndex + 0 ] = c.B;
                    colors[ currIndex + 1 ] = c.G;
                    colors[ currIndex + 2 ] = c.R;
                    colors[ currIndex + 3 ] = c.A;
                    currIndex += 4;
                }
            } );
            bmp.Fill( colors );
            return bmp;
        }

        /// <summary>
        /// Fills bytes BGRA into a bitmap
        /// </summary>
        public static void Fill( this Bitmap bmp, byte[] colors )
        {
            int width = bmp.Width;
            int height = bmp.Height;
            var data = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.ReadWrite, bmp.PixelFormat );
            Marshal.Copy( colors, 0, data.Scan0, width * height * 4 );
            bmp.UnlockBits( data );
        }
        /// <summary>
        /// Fills bytes BGRA into a bitmap
        /// </summary>
        public static void Fill( this Bitmap bmp, int[] colors )
        {
            int width = bmp.Width;
            int height = bmp.Height;
            var data = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.ReadWrite, bmp.PixelFormat );
            Marshal.Copy( colors, 0, data.Scan0, width * height );
            bmp.UnlockBits( data );
        }

        /// <summary>
        /// Converts a bitmap to a [y,x] array with (r,g,b,a,x,y) -> T
        /// </summary>
        public static T[ , ] ToArray<T>( this Bitmap bmp, Func<byte, byte, byte, byte, int, int, T> converter )
        {
            int width = bmp.Width;
            int height = bmp.Height;
            T[ , ] result = new T[ height, width ];
            byte[] colors = new byte[ width * height * 4 ];
            var data = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.ReadOnly, bmp.PixelFormat );
            Marshal.Copy( data.Scan0, colors, 0, width * height * 4 );
            bmp.UnlockBits( data );
            Parallel.For( 0, height, y =>
                                         {
                                             int currIndex = y * width * 4;
                                             for ( int x = 0 ; x < width ; ++x )
                                             {
                                                 result[ y, x ] = converter( colors[ currIndex + 2 ], colors[ currIndex + 1 ], colors[ currIndex + 0 ], colors[ currIndex + 3 ], x, y );
                                                 currIndex += 4;
                                             }
                                         } );
            return result;
        }
        /// <summary>
        /// Converts a bitmap to a [y,x] array with (r,g,b,a,x,y) -> T
        /// </summary>
        public static T[ , ] ToArrayMirrorY<T>( this Bitmap bmp, Func<byte, byte, byte, byte, int, int, T> converter )
        {
            int width = bmp.Width;
            int height = bmp.Height;
            T[ , ] result = new T[ height, width ];
            byte[] colors = new byte[ width * height * 4 ];
            var data = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.ReadOnly, bmp.PixelFormat );
            Marshal.Copy( data.Scan0, colors, 0, width * height * 4 );
            bmp.UnlockBits( data );
            Parallel.For( 0, height, y =>
                                         {
                                             int currIndex = y * width * 4;
                                             for ( int x = 0 ; x < width ; ++x )
                                             {
                                                 result[ height - y - 1, x ] = converter( colors[ currIndex + 2 ], colors[ currIndex + 1 ], colors[ currIndex + 0 ], colors[ currIndex + 3 ], x, y );
                                                 currIndex += 4;
                                             }
                                         } );
            return result;
        }

        /// <summary>
        /// Gets the center of a rect
        /// </summary>
        public static PointF Center( this RectangleF rect )
        {
            return new PointF( rect.Left + rect.Width / 2, rect.Top + rect.Height / 2 );
        }
        /// <summary>
        /// gets the distance to another point
        /// </summary>
        public static float DistanceTo( this PointF lhs, PointF rhs )
        {
            return ( float )Math.Sqrt( lhs.DistanceSqrTo( rhs ) );
        }
        /// <summary>
        /// gets the squared distance to another point
        /// </summary>
        public static float DistanceSqrTo( this PointF lhs, PointF rhs )
        {
            float dx = lhs.X - rhs.X;
            float dy = lhs.Y - rhs.Y;
            return dx * dx + dy * dy;
        }

        /// <summary>
        /// Creates a xml element and appends it to the document
        /// </summary>
        public static XmlElement CreateAndAppend( this XmlDocument doc, string name )
        {
            var ele = doc.CreateElement( name );
            doc.AppendChild( ele );
            return ele;
        }
        /// <summary>
        /// Creates a xml element and appends it to the node
        /// </summary>
        public static XmlElement CreateAndAppend( this XmlNode node, string name )
        {
            var ele = node.OwnerDocument.CreateElement( name );
            node.AppendChild( ele );
            return ele;
        }

        public static T GetRandomElement<T>( this IEnumerable<T> collection, Random r )
        {
            List<T> list = collection.ToList( );
            return list[ r.Next( list.Count ) ];
        }

        public static void Shuffle<T>( this List<T> list, Random r )
        {
            for ( int i = 0 ; i < list.Count ; ++i )
            {
                int newIndex = r.Next( list.Count );
                if ( newIndex == i ) continue;
                T save = list[ i ];
                list[ i ] = list[ newIndex ];
                list[ newIndex ] = save;
            }
        }

        /// <summary>
        /// removes possible starting slashes
        /// </summary>
        public static string NoStartingSlash( this string s )
        {
            if ( String.IsNullOrEmpty( s ) ) return s;
            return s.StartsWith( "/" ) ? s.Substring( 1 ) : s;
        }

        public static T GetWeightedRandom<T>( this IEnumerable<KeyValuePair<T, double>> collection, Random r )
        {
            double sum = collection.Sum( kvp => kvp.Value );
            double pos = r.NextBetween( 0, sum );
            foreach ( var kvp in collection )
            {
                if ( kvp.Value > pos ) return kvp.Key;
                pos -= kvp.Value;
            }
            throw new InvalidOperationException( );
        }

        public static double NextBetween( this Random r, double min, double max )
        {
            return min + ( max - min ) * ( double )r.NextDouble( );
        }

        public static string HashWithSHA256( this string s )
        {
            using ( var hash = SHA256.Create( ) )
            {
                byte[] bytes = Encoding.ASCII.GetBytes( s );
                var hashed = hash.ComputeHash( bytes );
                return BitConverter.ToString( hashed ).Replace( "-", "" );
            }
        }

        public static TEle ArgMin<TEle, TVal>( this IEnumerable<TEle> seq, Func<TEle, TVal> valueOf ) where TVal : IComparable<TVal>
        {
            TEle minEle = seq.First( );
            TVal minVal = valueOf( minEle );
            foreach ( var ele in seq )
            {
                TVal val = valueOf( ele );
                if ( val.CompareTo( minVal ) < 0 )
                {
                    minVal = val;
                    minEle = ele;
                }
            }
            return minEle;
        }
        public static TVal ArgMin<TEle, TRes, TVal>( this IList<TEle> seq, Func<TEle, TRes> valueOf, Func<TEle, int, TVal> selector ) where TRes : IComparable<TRes>
        {
            int minIndex = 0;
            TEle minEle = seq.First( );
            TRes minVal = valueOf( minEle );
            int i = 0;
            foreach ( var ele in seq )
            {
                TRes val = valueOf( ele );
                if ( val.CompareTo( minVal ) < 0 )
                {
                    minVal = val;
                    minEle = ele;
                    minIndex = i;
                }
                ++i;
            }
            return selector( minEle, minIndex );
        }

        /// <summary>
        /// YX-Rendering
        /// </summary>
        public static Bitmap Render<T>( this T[ , ] map, Func<T, Color> renderer )
        {
            return map.Render( ( obj, x, y ) => renderer( obj ) );
        }

        /// <summary>
        /// YX-Rendering
        /// </summary>
        public static Bitmap Render( this double[ , ] map, Func<double, Color> renderer, Bitmap ground )
        {
            Bitmap bmp = map.Render( renderer );
            Bitmap newBmp = new Bitmap( bmp.Width, bmp.Height );
            using ( Graphics g = Graphics.FromImage( newBmp ) )
            {
                g.DrawImage( ground, bmp.Width, bmp.Height );
                g.DrawImageUnscaled( bmp, 0, 0 );
            }
            return newBmp;
        }

        public static void DrawLine( this Graphics g, Pen pen, double x1, double y1, double x2, double y2 )
        {
            g.DrawLine( pen, ( float )x1, ( float )y1, ( float )x2, ( float )y2 );
        }

        public static void DrawRectangle( this Graphics g, Pen pen, double x1, double y1, double x2, double y2 )
        {
            g.DrawRectangle( pen, ( float )x1, ( float )y1, ( float )x2, ( float )y2 );
        }

        public static void DrawEllipse( this Graphics g, Pen pen, double x1, double y1, double x2, double y2 )
        {
            g.DrawEllipse( pen, ( float )x1, ( float )y1, ( float )x2, ( float )y2 );
        }

        public static void DrawArc( this Graphics g, Pen pen, double x1, double y1, double x2, double y2, double a1, double a2 )
        {
            g.DrawArc( pen, ( float )x1, ( float )y1, ( float )x2, ( float )y2, ( float )a1, ( float )a2 );
        }

        public static void FillRectangle( this Graphics g, Brush brush, double x1, double y1, double x2, double y2 )
        {
            g.FillRectangle( brush, ( float )x1, ( float )y1, ( float )x2, ( float )y2 );
        }

        public static void FillEllipse( this Graphics g, Brush brush, double x1, double y1, double x2, double y2 )
        {
            g.FillEllipse( brush, ( float )x1, ( float )y1, ( float )x2, ( float )y2 );
        }

        public static void FillArc( this Graphics g, Brush brush, double x1, double y1, double x2, double y2, double a1, double a2 )
        {
            g.FillArc( brush, ( float )x1, ( float )y1, ( float )x2, ( float )y2, ( float )a1, ( float )a2 );
        }

        public static string Capitalized( this string s )
        {
            if ( s == null ) return null;
            if ( s.Length == 0 ) return s;
            return s.Substring( 0, 1 ).ToUpper( ) + s.Substring( 1 ).ToLower( );
        }

        public static Color ColorLerp( this double x, Color minC, Color maxC, double min = 0, double max = 1 )
        {
            double alpha = Helpers.Saturate( ( x - min ) / ( max - min ) );
            return Color.FromArgb(
                ( int )Helpers.LerpSaturate( alpha, minC.A, maxC.A ),
                ( int )Helpers.LerpSaturate( alpha, minC.R, maxC.R ),
                ( int )Helpers.LerpSaturate( alpha, minC.G, maxC.G ),
                ( int )Helpers.LerpSaturate( alpha, minC.B, maxC.B )
                );
        }
    }
}